using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class BuildScreen : MonoBehaviour {
	// Parameters for fine tuning the Build Screen.
	public float speed 				= 0.5f,
				 joystickThreshold 	= 0.1f,
				 maxLootDist 		= 3.0f,
				 dropDistance		= 2.0f;
	
	// Public parameters.
	public Camera worldCamera, buildCamera, minimapCamera;
	public MiniMap minimap;
	
	// Containers passed to NetworkEntityMgr.
	public Transform inventoryContainer,
					 buildScreenContainer,
					 weaponContainer;

	// The material used to show a preview in the BuildScreen of 
	// where a component will lock.
	public Material previewMaterial;
	
	// The locations where components and weapons will appear in
	// the build screen.
	public Vector3 weaponInitialPosition 	= new Vector3(0, 0, 0),
				   componentInitialPosition = new Vector3(1, 1, 0);
	
	// Input channel names.
	public string rotateWeaponX 		= "BuildRotateWeaponX",
				  rotateWeaponY 		= "BuildRotateWeaponY",
				  rotateComponentX 		= "BuildRotateComponentX",
				  rotateComponentY 		= "BuildRotateComponentY",
				  moveComponentX 		= "BuildMoveComponentX",
				  moveComponentY 		= "BuildMoveComponentY",
				  lockComponentButton 	= "BuildLock",
				  lootButton 			= "Loot",
				  dumpLootButton 		= "DumpLoot",
				  toggleBuildScreen		= "ToggleBuildScreen",
				  switchWeapon	 		= "InventoryRotateLeft",
				  switchComponent 		= "InventoryRotateRight";

	// Private members.
	private EntityMgr entityMgr	= null;
	private Entity weapon 		= null;
	private Entity component 	= null;
	private Entity preview		= null;
	
	// The NetworkEntityMgr. Can be set using the setter at the bottom.
	// Is called by NetworkStart::SpawnPlayer() during initiation.
	private NetworkEntityMgr networkEntityMgr;
	private DuctTaper ductTapeMgr;
	private GUIHealthBar healthbar;
	private PlayerGUI playergui;
	private Camera buildScreenBackground;
	
	// Called during the initiation of the Player.
	public void Start() {
		entityMgr = new EntityMgr(networkEntityMgr);
		if(buildScreenContainer != null)
			networkEntityMgr.setBuildScreenContainer(buildScreenContainer);
		if(inventoryContainer != null)
			networkEntityMgr.setInventoryContainer(inventoryContainer);
		if(weaponContainer != null)
			networkEntityMgr.setWeaponContainer(weaponContainer);
		if(worldCamera != null)
			networkEntityMgr.setEmitter(worldCamera.GetComponent<ParticleSystem>());
	}
	
	// Removes the Preview node.
	public void removePreview() {
		if (preview != null) {
			preview.releaseLock();
			
			preview.each(delegate(GameObject obj) {
				Destroy(obj);
				return false;
			});

			preview = null;
		}
	}
	
	// Called each step.
	public void Update () {
		
		// Toggle Build Screen.
		if (Input.GetButtonDown(toggleBuildScreen)) {

			// If the Build screen should close.
			if (buildCamera.enabled) {
				buildCamera.enabled = false;
				//worldCamera.enabled = true;
				
				// Toggle Build Screen Background
				if (buildScreenBackground != null) {
					buildScreenBackground.enabled = false;
				} else {
					Debug.LogWarning("There are no Build Screen Background attached to BuildScreen.");
				}
				
				// Toggle Minimap
				if (minimapCamera != null) {
					minimapCamera.enabled = true;
				} else {
					Debug.LogWarning("There are no Minimap Camera attached to BuildScreen.");
				}
				
				if (minimap != null) {
					minimap.enabled = true;
				} else {
					Debug.LogWarning("There are no Minimap Script attached to BuildScreen.");
				}
				
				// Toggle healthbar
				if (healthbar != null) {
					healthbar.enabled = true;
				} else {
					Debug.LogWarning("There are no GUIHealthbar attached to BuildScreen.");
				}
				
				// But back weapon.
				if (weapon != null) {
					entityMgr.fromBuildScreen(weapon);
					weapon = null;
				}
				
				// Put back component.
				if (component != null) {
					entityMgr.fromBuildScreen(component);
					component = null;
				}
				
				// Toggle crosshair
				if (playergui != null) {
					playergui.enabled = true;
				} else {
					Debug.LogWarning("There are no PlayerGUI attached to BuildScreen.");
				}
				
				// Remove preview.
				removePreview();
				
				// Notify listeners.
				Listeners.inst().notify(Channel.ON_LEFT_BUILDSCREEN, null);
				
			// If the Build Screen should open.
			} else {
				buildCamera.enabled = true;
				//worldCamera.enabled = false;
				
				// Toggle Build Screen Background
				if (buildScreenBackground != null) {
					buildScreenBackground.enabled = true;
				} else {
					Debug.LogWarning("There are no Build Screen Background attached to BuildScreen.");
				}
				
				// Toggle Minimap
				if (minimapCamera != null) {
					minimapCamera.enabled = false;
				} else {
					Debug.LogWarning("There are no Minimap Camera attached to BuildScreen.");
				}
				
				if (minimap != null) {
					minimap.enabled = false;
				} else {
					Debug.LogWarning("There are no Minimap Script attached to BuildScreen.");
				}
				
				// Toggle healthbar
				if (healthbar != null) {
					healthbar.enabled = false;
				} else {
					Debug.LogWarning("There are no GUIHealthbar attached to BuildScreen.");
				}
				
				// Toggle crosshair
				if (playergui != null) {
					playergui.enabled = false;
				} else {
					Debug.LogWarning("There are no PlayerGUI attached to BuildScreen.");
				}
				
				// Weapon and component will be added if nescessary later on.
				// Notify listeners.
				Listeners.inst().notify(Channel.ON_ENTERED_BUILDSCREEN, null);
			}
		}
			
		// If the build screen is open.
		if (buildCamera.enabled) {

			if (preview != null) {
				preview.releaseLock();
			}
			
			// TODO if component is last item, make it weapon.
			// TODO rotate around real center and not root center.
			
			if (Input.GetButtonDown(switchComponent)) {
				// Make component weapon.
				if (component != null) {
					entityMgr.fromBuildScreen(component);
				}
				
				component = entityMgr.toBuildScreen(componentInitialPosition);
			}
			
			if (Input.GetButtonDown(switchWeapon)) {
				// Make component weapon.
				if (weapon != null) {
					entityMgr.fromBuildScreen(weapon);
				}
				
				weapon = entityMgr.toBuildScreen(weaponInitialPosition);
			}
			
			// Rotate weapon
			if (weapon != null) {
				getJoystick(rotateWeaponX, rotateWeaponY, delegate(Vector3 dir) {
					weapon.transform.RotateAroundLocal(Vector3.up, -dir.x * speed);
					weapon.transform.RotateAroundLocal(Vector3.right, -dir.y * speed);
				});
				
				// Rotate and move component
				if (component != null) {

					// If the component is locked, null it so that a new one can take its place.
					if (component.isLocked()) {
						Debug.Log("Component is already locked, even though it should not be. It will be removed the next step.");
						component = null;
						removePreview();
						
					// The component is not locked.
					} else {
					
						// Handle rotation
						getJoystick(rotateComponentX, rotateComponentY, delegate(Vector3 dir) {
							component.transform.RotateAroundLocal(Vector3.up, -dir.x * (speed / 4.0f));
							component.transform.RotateAroundLocal(Vector3.right, -dir.y * (speed / 4.0f));
						});
						
						// Handle movement
						getJoystick(moveComponentX, moveComponentY, delegate(Vector3 dir) {
							Vector3 delta = buildCamera.cameraToWorldMatrix * dir * speed;
							component.transform.position += delta;
						});

						// Check for lock key.
						if (Input.GetButtonDown(lockComponentButton)) {
							if (component.lockTowards(weapon)) {
								if (ductTapeMgr != null) {
									ductTapeMgr.tapeItem(component.getLocalPoint().transform, component.transform);
								}
								
								component = null;
								removePreview();
								
								// Notify listeners.
								Listeners.inst().notify(Channel.ON_BUILD_LOCKED, null);
							}
						// The lock key was not pressed.
						} else {
							
							// If there are no preview object visible.
							if (preview == null) {
								// Create one.
								GameObject temp = GameObject.Instantiate(component.gameObject) as GameObject;
								preview = temp.GetComponent<Entity>();
								if(previewMaterial != null)
									preview.setMaterial(previewMaterial);
							}
							
							// Move the preview object to the correct position.
							preview.transform.position = component.transform.position;
							preview.transform.rotation = component.transform.rotation;
							preview.lockTowards(weapon);
						}
					}

				// There are no component in the build screen. Try adding one.
				} else if (entityMgr.hasItem()) {
					component = entityMgr.toBuildScreen(componentInitialPosition);
				}
				
			// There are no weapon in the build screen. Try adding one.
			} else if (entityMgr.hasItem()) {
				weapon = entityMgr.toBuildScreen(weaponInitialPosition);
			}

			// TODO switch item
			// TODO switch weapon
			
		} else {
			handleLoot();
			dumpLoot();
		}
	}
	
	private void handleLoot() {
		LayerRay ray = new LayerRay(
			worldCamera.transform.position, 
			worldCamera.transform.forward, 
			LayerMgr.LAYER_WORLD
		);
		
		// If the player hits the loot button.
		if ((buildCamera == null || !buildCamera.enabled) && Input.GetButtonDown(lootButton)) {
			
			// Find any item targeted.
			if (ray.raycast(maxLootDist)) {
				if (ray.getHit().collider.gameObject.GetComponent<Spuddable>() != null) {
					if (gameObject.GetComponent<RigidbodyFPSController>().GetSpuds() != 0) {
						Spuddable snake = ray.getHit().collider.GetComponent<Spuddable>();
						if (snake.GetPicked()) {
							BarrelPoint barrel = gameObject.GetComponent<WeaponSystem>().GetFirstBarrelpoint();
							if (barrel != null) {
								gameObject.GetComponent<WeaponSystem>().fillSpuds(snake.gameObject);
							}
						}
					}
					
				// To find an Entity, it must contain at least one collider exactly one step
				// below itself in the hierarchy. An Entity may contain several colliders.
				} else if (ray.getHit().collider.transform.parent != null) {
					Entity entity = ray.getHit().collider.transform.parent.GetComponent<Entity>();
					
//					if (entity.GetComponent<Spuddable>() != null){
//						Debug.Log ("Spuddable");
//						if(gameObject.GetComponent<RigidbodyFPSController>().GetSpuds() != 0){
//							Debug.Log ("Spuddable");
//							Spuddable snake = ray.getHit().collider.transform.parent.GetComponent<Spuddable>();
//							if(snake.GetPicked()){
//								BarrelPoint barrel = gameObject.GetComponent<WeaponSystem>().GetFirstBarrelpoint();
//								
//								if(barrel != null){
//									gameObject.GetComponent<WeaponSystem>().fillSpuds(snake.GetComponent<Entity>());
//								}
//							}
//						}
//					}
					
					// If an item was found, check if it is a lootable item.
					if (entity != null) {
						
						ItemStatBoost itemStatBoost = entity.GetComponent<ItemStatBoost>();
						
						if (itemStatBoost != null 
						&& itemStatBoost.instantUse 
						&& this.GetComponent<RigidbodyFPSController>() != null) {
							
							itemStatBoost.ApplyBoost(this.GetComponent<RigidbodyFPSController>());
							Destroy(entity.gameObject);
							
							// Notify listeners.
							Listeners.inst().notify(Channel.ON_CONSUME_ENTITY, GetComponent<PlayerState>());
							
						// Find the GameObject and move it to the inventory.
						} else {
							entityMgr.fromWorld(entity);
							
							// Notify listeners.
							Listeners.inst().notify(Channel.ON_LOOT_ENTITY, GetComponent<PlayerState>());
						}
					}
				}
			}
		}
	}
	
	// CHeck if an item should be dropped on the ground.
	private void dumpLoot() {
		
		// If the button was pressed, drop the loot.
		if (Input.GetButtonDown(dumpLootButton)) {
			entityMgr.toWorld(transform.position + transform.forward * dropDistance);
		}
	} 
	
	// Calls the specified function if a joystick was pressed more than the 
	// minimum amount. The resulting value will be an exponential.
	private void getJoystick(string inputX, string inputY, System.Action<Vector3> onInput) {
		var directionVector = new Vector3(
			Input.GetAxis(inputX), 
			Input.GetAxis(inputY), 
			0
		);
		
		float directionLength = directionVector.magnitude;
		
		if (directionLength > joystickThreshold) {
			// Get the length of the directon vector and then normalize it
			// Dividing by the length is cheaper than normalizing when we already have the length anyway
			directionVector = directionVector / directionLength;
			
			// Make sure the length is no bigger than 1
			directionLength = Mathf.Min(1, directionLength);
			
			// Make the input vector more sensitive towards the extremes and less sensitive in the middle
			// This makes it easier to control slow speeds when using analog sticks
			directionLength = directionLength * directionLength;
			
			// Multiply the normalized direction vector by the modified length
			directionVector = directionVector * directionLength;
			
			if (onInput != null) {
				onInput(directionVector);
			}
		}
	}
	
	// Returns the EntityMgr.
	public EntityMgr getEntityMgr() {
		return entityMgr;	
	}
	
	// Used to set the NetworkEntityMgr. Used by NetworkStart::SpawnPlayer().
	public void setNetworkEntityMgr(NetworkEntityMgr networkEntityMgr) {
		this.networkEntityMgr = networkEntityMgr;	
	}
	
	public void setDuctTapeMgr(DuctTaper ductTapeMgr) {
		this.ductTapeMgr = ductTapeMgr;	
	}
	
	public void setHealthbar(GUIHealthBar healthbar) {
		this.healthbar = healthbar;	
	}
	
	public void setPlayerGUI(PlayerGUI playergui) {
		this.playergui = playergui;	
	}
	
	public void setBuildScreenBackground(Camera buildScreenBackground) {
		this.buildScreenBackground = buildScreenBackground;
	}
}
